//
// Created by tacom on 2024/9/26.
//

#include "spi_transfer.h"
#include "hardware/dma.h"
#include "pico/time.h"

// data buffer
uint8_t out_buf[BUF_LEN], in_buf[BUF_LEN];
// dma support
static uint dma_tx, dma_rx;


void spi_slave_init(){
    // spi default
    spi_init(spi_default, SPI_SPEED);
    spi_set_slave(spi_default, true);
    spi_set_format(spi_default, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);
    gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI);
    gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI);
    gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI);
    gpio_set_function(PICO_DEFAULT_SPI_CSN_PIN, GPIO_FUNC_SPI);

    // dma init
    dma_tx = dma_claim_unused_channel(true);
    dma_rx = dma_claim_unused_channel(true);
}

int __not_in_flash_func(spi_write_read_blocking_write)(spi_inst_t *spi, const uint8_t *src, size_t len) {
    invalid_params_if(HARDWARE_SPI, 0 > (int)len);

    // Never have more transfers in flight than will fit into the RX FIFO,
    // else FIFO will overflow if this code is heavily interrupted.
    size_t tx_remaining = len;

    while (tx_remaining) {
        if (spi_is_writable(spi)) {
            spi_get_hw(spi)->dr = (uint32_t) *src++;
            --tx_remaining;
            if(tx_remaining % 2 == 0) printf("tx_remaining is: %d\n", tx_remaining);
        }
    }

    return (int)len;
}



int spi_slave_transfer_data(bool to_host, size_t len){
    // printf("spi_transfer: send is: %s, len: %d\n", out_buf, len);
    if(to_host){
//        return spi_write_read_blocking_write(spi_default, out_buf, len);
//        return spi_write_read_blocking(spi_default, out_buf, in_buf, len);
        return spi_write_blocking(spi_default, out_buf, len);
    }else{
        // 0x0F = 15
        return spi_read_blocking(spi_default, 15, in_buf, len);
    }
}

void spi_slave_transfer_with_dma(bool to_host, size_t len){
    uint32_t start_time = time_us_32();
    dma_channel_config c;
    if(to_host){
        c = dma_channel_get_default_config(dma_tx);
        channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
        channel_config_set_dreq(&c, spi_get_dreq(spi_default, true));
        dma_channel_configure(dma_tx, &c,
                              &spi_get_hw(spi_default)->dr, // write address
                              out_buf, // read address
                              len, // element count (each element is of size transfer_data_size)
                              true); // start yet
        dma_channel_wait_for_finish_blocking(dma_tx);
    }else{
        c = dma_channel_get_default_config(dma_rx);
        channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
        channel_config_set_dreq(&c, spi_get_dreq(spi_default, false));
        channel_config_set_read_increment(&c, false);
        channel_config_set_write_increment(&c, true);
        dma_channel_configure(dma_rx, &c,
                              in_buf, // write address
                              &spi_get_hw(spi_default)->dr, // read address
                              len, // element count (each element is of size transfer_data_size)
                              true); // don't start yet
        dma_channel_wait_for_finish_blocking(dma_rx);
    }

    uint32_t finish_time = time_us_32();
    float elapsed_time_s = 1e-6f * (finish_time - start_time);
    printf("Transfer speed: %.3f MB/s\n", (sizeof(uint8_t) * len / 1e6f) / elapsed_time_s);
}

void spi_loop_dma_test(){
    uint32_t start_time = time_us_32();

    // Force loopback for testing (I don't have an SPI device handy)
    hw_set_bits(&spi_get_hw(spi_default)->cr1, SPI_SSPCR1_LBM_BITS);

    // set send
    dma_channel_config c;
    c = dma_channel_get_default_config(dma_tx);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_dreq(&c, spi_get_dreq(spi_default, true));
    dma_channel_configure(dma_tx, &c,
                          &spi_get_hw(spi_default)->dr, // write address
                          out_buf, // read address
                          BUF_LEN, // element count (each element is of size transfer_data_size)
                          false); // don't start yet

    // set recv
    c = dma_channel_get_default_config(dma_rx);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_dreq(&c, spi_get_dreq(spi_default, false));
    channel_config_set_read_increment(&c, false);
    channel_config_set_write_increment(&c, true);
    dma_channel_configure(dma_rx, &c,
                          in_buf, // write address
                          &spi_get_hw(spi_default)->dr, // read address
                          BUF_LEN, // element count (each element is of size transfer_data_size)
                          false); // don't start yet

    // start both
    printf("Starting DMAs...\n");
    dma_start_channel_mask((1u << dma_tx) | (1u << dma_rx));

    printf("Wait for RX complete...\n");
//    dma_channel_wait_for_finish_blocking(dma_tx);
    dma_channel_wait_for_finish_blocking(dma_rx);

    if (dma_channel_is_busy(dma_tx)) {
        panic("RX completed before TX");
    }

    printf("Done. Checking...");
    for (uint i = 0; i < BUF_LEN; ++i) {
        if (out_buf[i] != in_buf[i]) {
            panic("Mismatch at %d/%d: expected %02x, got %02x",
                  i, BUF_LEN, out_buf[i], in_buf[i]
            );
        }
    }

    dma_channel_unclaim(dma_tx);
    dma_channel_unclaim(dma_rx);

    uint32_t finish_time = time_us_32();
    float elapsed_time_s = 1e-6f * (finish_time - start_time);
    printf("Transfer speed: %.3f MB/s\n", (sizeof(uint8_t) * BUF_LEN / 1e6f) / elapsed_time_s);
}